// $Id: spaceship.cpp 361 2008-04-26 16:07:26Z bo $
// Implementation of the barrel class
//
// ml278
// Copyright (C) 2008 Bjørn Øivind Bjørnsen, Aleksander Gjermundsen, Knut Andre Tidemann, Thomas Hansen
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

#include "Ogre.h"
#include "keybindingsmanager.h"
#include <iostream>
#include <ios>
#include <iomanip>
#include <sstream>

using namespace Ogre;
using namespace std;
using namespace ml278;

/**
 * constructor with reference to the filename used for storing keybindings
 * @param filename string containing the filename to use for the textfile in which to store keybindings
 */
KeyBindingsManager::KeyBindingsManager(const String &filename)
{
        keyNames[KEY_FORWARD] = "KEY_FORWARD";
        keyNames[KEY_BACKWARD] = "KEY_BACKWARD";
        keyNames[KEY_YAW_LEFT] = "KEY_YAW_LEFT";
        keyNames[KEY_YAW_RIGHT] = "KEY_YAW_RIGHT";
        keyNames[KEY_PITCH_UP] = "KEY_PITCH_UP";
        keyNames[KEY_PITCH_DOWN] = "KEY_PITCH_DOWN";
        keyNames[KEY_SCREENSHOT] = "KEY_SCREENSHOT";
        keyNames[KEY_KEYS_OVERLAY] = "KEY_KEYS_OVERLAY";
        keyNames[KEY_STATS_OVERLAY] = "KEY_STATS_OVERLAY";
        keyNames[KEY_SWITCH_TEXTURE_FILTERING] = "KEY_SWITCH_TEXTURE_FILTERING";
        keyNames[KEY_SWITCH_POLYGON_MODE] = "KEY_SWITCH_POLYGON_MODE";
        keyNames[KEY_CAMERA_FIRST_PERSON] = "KEY_CAMERA_FIRST_PERSON";
        keyNames[KEY_CAMERA_THIRD_PERSON] = "KEY_CAMERA_THIRD_PERSON";
	keyNames[KEY_CAMERA_FREEROAM] = "KEY_CAMERA_FREEROAM";
        keyNames[KEY_TOGGLE_GUI] = "KEY_TOGGLE_GUI";
        keyNames[KEY_QUIT] = "KEY_QUIT";

	// Text used in to describe the key to end-users
	keyBindingNames[KEY_FORWARD] = "Forward";
        keyBindingNames[KEY_BACKWARD] = "Backward";
        keyBindingNames[KEY_YAW_LEFT] = "Turn left";
        keyBindingNames[KEY_YAW_RIGHT] = "Turn right";
        keyBindingNames[KEY_PITCH_UP] = "Turn up";
        keyBindingNames[KEY_PITCH_DOWN] = "Turn down";
        keyBindingNames[KEY_SCREENSHOT] = "Screenshot";
        keyBindingNames[KEY_KEYS_OVERLAY] = "Keys overlay";
        keyBindingNames[KEY_STATS_OVERLAY] = "Stats overlay";
        keyBindingNames[KEY_SWITCH_TEXTURE_FILTERING] = "Texture filtering";
        keyBindingNames[KEY_SWITCH_POLYGON_MODE] = "Polygon mode";
        keyBindingNames[KEY_CAMERA_FIRST_PERSON] = "First person camera";
        keyBindingNames[KEY_CAMERA_THIRD_PERSON] = "Third person camera";
	keyBindingNames[KEY_CAMERA_FREEROAM] = "Free-roaming camera";
        keyBindingNames[KEY_TOGGLE_GUI] = "Menu";
        keyBindingNames[KEY_QUIT] = "Quit";

        defaultKeys[KEY_FORWARD] = OIS::KC_W;
        defaultKeys[KEY_BACKWARD] = OIS::KC_S;
        defaultKeys[KEY_YAW_LEFT] = OIS::KC_A;
        defaultKeys[KEY_YAW_RIGHT] = OIS::KC_D;
        defaultKeys[KEY_PITCH_UP] = OIS::KC_E;
        defaultKeys[KEY_PITCH_DOWN] = OIS::KC_Q;
        defaultKeys[KEY_SCREENSHOT] = OIS::KC_SYSRQ;
        defaultKeys[KEY_KEYS_OVERLAY] = OIS::KC_L;
        defaultKeys[KEY_STATS_OVERLAY] = OIS::KC_F;
        defaultKeys[KEY_SWITCH_TEXTURE_FILTERING] = OIS::KC_T;
        defaultKeys[KEY_SWITCH_POLYGON_MODE] = OIS::KC_R;
        defaultKeys[KEY_CAMERA_FIRST_PERSON] = OIS::KC_F1;
        defaultKeys[KEY_CAMERA_THIRD_PERSON] = OIS::KC_F2;
	defaultKeys[KEY_CAMERA_FREEROAM] = OIS::KC_C;
        defaultKeys[KEY_TOGGLE_GUI] = OIS::KC_ESCAPE;
        defaultKeys[KEY_QUIT] = OIS::KC_F4;
        mFilename = filename;

        // Load the configuration from file or load the defaults if it fails
        if(!initKeys())
        {
                for(unsigned int i=0;i<NUM_BINDED_KEYS;i++)
                        keys[i] = defaultKeys[i];
        }
                
}

/**
 * destructor
*/
KeyBindingsManager::~KeyBindingsManager()
{
}

/**
 * load the keybindings from the configuration file defined
 */
bool KeyBindingsManager::initKeys()
{
	std::ifstream fs;
	fs.open(mFilename.c_str(), std::ios::in | std::ios::binary);
	if (fs)
	{
		// Wrap as a stream
		DataStreamPtr stream (new FileStreamDataStream (mFilename, &fs, false));
                ConfigFile cf;
	        cf.load(stream);

                for(unsigned int i=0;i<NUM_BINDED_KEYS;i++)
		{
			// Read setting from configuration file or use default if it is missing
			if(cf.getSetting(keyNames[i]) != StringUtil::BLANK)
	                	keys[i] = (OIS::KeyCode)StringConverter::parseInt(cf.getSetting(keyNames[i]));
			else
				keys[i] = defaultKeys[i];
		}

                return true;
	}
        else
                return false;
}

/**
 * retrieves a vector with the default key bindings defined by the class
 * @return vector with the default key bindings
 */
vector<OIS::KeyCode> KeyBindingsManager::getDefaultKeys()
{
        vector<OIS::KeyCode> returnVector;

        for(unsigned int i=0;i<NUM_BINDED_KEYS;i++)
                returnVector.push_back(defaultKeys[i]);

        return returnVector;
}

/**
 * reverts the keybindings in the configuration file to default values
 * @return true if the configuration file could be written
 */
bool KeyBindingsManager::saveDefaultKeys()
{
        vector<OIS::KeyCode> defaultKeysVector = getDefaultKeys();        
        return saveAllKeys(&defaultKeysVector);
}

/**
 * saves a whole set of keybinding to the configuration file
 * @param newKeys a vector with all the keybindings
 * @pre newKeys must contain ALL keybindings, this size can be retrieved with getNumBindings()
 * @return true if the configuration file could be written
 */
bool KeyBindingsManager::saveAllKeys(const vector<OIS::KeyCode> *newKeys)
{
	// Save the whole set of keybinding to file
        assert(newKeys->size() == NUM_BINDED_KEYS);

	ifstream cfgInFile(mFilename.c_str());
	bool existingFile;
	unsigned int foundIndex;
        string s;
        string ssString;
        stringstream ss;
        string::size_type beginValue;
        string::size_type endValue;
        stringstream stringStreamValue;

        // If the configuration file could not be opened for reading
        if(!cfgInFile)
	{
        	existingFile = false;
	}
	else
	{
		existingFile = true;
		while(!cfgInFile.eof())
		{
			getline(cfgInFile,s);
			ss << s << endl;
		}
        	ssString = ss.str();
                // Remove trailing newline after the last loop iteration
                if(ssString[ssString.size()-2] == '\n' && ssString[ssString.size()-1] == '\r')
                        ssString.resize(ssString.size()-2);
                else if(ssString[ssString.size()-1] == '\n')
                        ssString.resize(ssString.size()-1);
	}
	// Close up input from the file before writing back
	cfgInFile.clear();
	cfgInFile.close(); 
	
        for(unsigned int i=0;i<NUM_BINDED_KEYS;i++)
        {
		if(existingFile)
		{
                	foundIndex = ssString.find(keyNames[i],0);
			
			// If a value is present, overwrite that line
			if(foundIndex != string::npos)
			{
				stringStreamValue << "=" << newKeys->at(i);
				beginValue = foundIndex + keyNames[i].size();   // Set to position after the key-name, before equal-sign and value
				endValue = ssString.find("\n",beginValue);      //Find position of first newline after the value
				ssString.erase(beginValue,(endValue - beginValue));     // Erase from before the equal sign to the first newline
				ssString.insert(beginValue, stringStreamValue.str());
			}
		}
                // If a value does not already exist in the configuration file
                if(!existingFile || foundIndex == string::npos)
                {
                        stringStreamValue << keyNames[i] << "=" << newKeys->at(i) << endl;
                        ssString.append(stringStreamValue.str());
                }
		stringStreamValue.str("");
        }
	
	ss.str(ssString);
	
        // Overwrite the existing file and insert the finished string
        ofstream cfgOutFile(mFilename.c_str(), ios::out | ios::trunc);
        
        // If the configuration file could not be opened for writing
        if(!cfgOutFile)
                return false;

	// Write string back to file
        cfgOutFile << ss.str();
        cfgOutFile.close();
 	
	// Last check too see if errors has occured
	// If errors occured on the input file object, if a file existed
	if(existingFile && !cfgInFile)
       		return false;
	// If errors occured on the the output file object
	if(!cfgOutFile)
		return false;

        // Reload keys from disk
        if(!initKeys())
		return false;
        
        return true;
}

/**
 * sets a whole set of keybinding in the manager, will be overridden next time initKeys() is run
 * @param newKeys a vector with all the keybindings
 * @pre newKeys must contain ALL keybindings, this size can be retrieved with getNumBindings()
 */
void KeyBindingsManager::setTempAllKeys(const vector<OIS::KeyCode> *newKeys)
{
        assert(newKeys->size() == NUM_BINDED_KEYS);

        for(unsigned int i=0;i<NUM_BINDED_KEYS;i++)
        {
                keys[i] = newKeys->at(i);
        }
}

/**
 * returns the actual (OIS)keycode corresponding to a keybinding defined in the manager
 * @param keyBinding the keybinding assigned a key
 * @return the keycode of the keybinding
 */
OIS::KeyCode KeyBindingsManager::getKey(ml278::KeyBindings keyBinding)
{
        return keys[keyBinding];
}

/**
 * returns the total number of keybindings registered in the manager
 * @return the number of managed keybindings
 */
int KeyBindingsManager::getNumBindings()
{
        return NUM_BINDED_KEYS;
}

/**
 * converts a keycode into a string that can be used to describe the key to end-users
 * @param key the keycode to retrieve a name for
 * @return the descriptive name of that key
 */
string KeyBindingsManager::getKeyCodeString(OIS::KeyCode key)
{
        using namespace OIS;
        string kc;

        switch (key)
        {
                case KC_A: kc = "A"; break;
		case KC_B: kc = "B"; break;
		case KC_C: kc = "C"; break;
		case KC_D: kc = "D"; break;
		case KC_E: kc = "E"; break;
		case KC_F: kc = "F"; break;
		case KC_G: kc = "G"; break;
		case KC_H: kc = "H"; break;
		case KC_I: kc = "I"; break;
		case KC_J: kc = "J"; break;
		case KC_K: kc = "K"; break;
		case KC_L: kc = "L"; break;
		case KC_M: kc = "M"; break;
		case KC_N: kc = "N"; break;
		case KC_O: kc = "O"; break;
		case KC_P: kc = "P"; break;
		case KC_Q: kc = "Q"; break;
		case KC_R: kc = "R"; break;
		case KC_S: kc = "S"; break;
		case KC_T: kc = "T"; break;
		case KC_U: kc = "U"; break;
		case KC_V: kc = "V"; break;
		case KC_W: kc = "W"; break;
		case KC_X: kc = "X"; break;
		case KC_Y: kc = "Y"; break;
		case KC_Z: kc = "Z"; break;
		case KC_0: kc = "0"; break;
		case KC_1: kc = "1"; break;
		case KC_2: kc = "2"; break;
		case KC_3: kc = "3"; break;
		case KC_4: kc = "4"; break;
		case KC_5: kc = "5"; break;
		case KC_6: kc = "6"; break;
		case KC_7: kc = "7"; break;
		case KC_8: kc = "8"; break;
		case KC_9: kc = "9"; break;
		case KC_F1: kc = "F1"; break;
		case KC_F2: kc = "F2"; break;
		case KC_F3: kc = "F3"; break;
		case KC_F4: kc = "F4"; break;
		case KC_F5: kc = "F5"; break;
		case KC_F6: kc = "F6"; break;
		case KC_F7: kc = "F7"; break;
		case KC_F8: kc = "F8"; break;
		case KC_F9: kc = "F9"; break;
		case KC_F10: kc = "F10"; break;
		case KC_F11: kc = "F11"; break;
		case KC_F12: kc = "F12"; break;
		case KC_F13: kc = "F13"; break;
		case KC_F14: kc = "F14"; break;
		case KC_F15: kc = "F15"; break;
		case KC_UNASSIGNED: kc = "Unassigned"; break;
		case KC_ESCAPE: kc = "Esc"; break;
		case KC_MINUS: kc = "-"; break;
		case KC_EQUALS: kc = "="; break;
		case KC_BACK: kc = "Backspace"; break;
		case KC_TAB: kc = "Tab"; break;
		case KC_LBRACKET: kc = "["; break;
		case KC_RBRACKET: kc = "]"; break;
		case KC_RETURN: kc = "Enter"; break;
		case KC_LCONTROL: kc = "Left ctrl"; break;
		case KC_SEMICOLON: kc = ";"; break;
		case KC_APOSTROPHE: kc = "Apostrophe"; break;
		case KC_GRAVE: kc = "Grave"; break;
		case KC_LSHIFT: kc = "Left shift"; break;
		case KC_BACKSLASH: kc = "\\"; break;
		case KC_COMMA: kc = ","; break;
		case KC_PERIOD: kc = "."; break;
		case KC_SLASH: kc = "/"; break;
		case KC_RSHIFT: kc = "Right shift"; break;
		case KC_MULTIPLY: kc = "*"; break;
		case KC_LMENU: kc = "Left Alt"; break;
		case KC_SPACE: kc = "Space"; break;
		case KC_CAPITAL: kc = "Capital"; break;
		case KC_NUMLOCK: kc = "Num Lock"; break;
		case KC_SCROLL: kc = "Scroll"; break;
		case KC_NUMPAD7: kc = "Numpad 7"; break;
		case KC_NUMPAD8: kc = "Numpad 8"; break;
		case KC_NUMPAD9: kc = "Numpad 9"; break;
		case KC_SUBTRACT: kc = "Subtract"; break;
		case KC_NUMPAD4: kc = "Numpad 4"; break;
		case KC_NUMPAD5: kc = "Numpad 5"; break;
		case KC_NUMPAD6: kc = "Numpad 6"; break;
		case KC_ADD: kc = "+"; break;
		case KC_NUMPAD1: kc = "Numpad 1"; break;
		case KC_NUMPAD2: kc = "Numpad 2"; break;
		case KC_NUMPAD3: kc = "Numpad 3"; break;
		case KC_NUMPAD0: kc = "Numpad 0"; break;
		case KC_DECIMAL: kc = "Decimal"; break;
		case KC_OEM_102: kc = "OEM 102"; break;
		case KC_KANA: kc = "KANA"; break;
		case KC_ABNT_C1: kc = "ABNT C1"; break;
		case KC_CONVERT: kc = "Convert"; break;
		case KC_NOCONVERT: kc = "No convert"; break;
		case KC_YEN: kc = "Yen"; break;
		case KC_ABNT_C2: kc = "ABNT C2"; break;
		case KC_NUMPADEQUALS: kc = "Numpad ="; break;
		case KC_PREVTRACK: kc = "Previous track"; break;
		case KC_AT: kc = "@"; break;
		case KC_COLON: kc = ":"; break;
		case KC_UNDERLINE: kc = "_"; break;
		case KC_KANJI: kc = "Kanji"; break;
		case KC_STOP: kc = "Stop"; break;
		case KC_AX: kc = "Ax"; break;
		case KC_UNLABELED: kc = "Unlabeled"; break;
		case KC_NEXTTRACK: kc = "Next track"; break;
		case KC_NUMPADENTER: kc = "Numpad enter"; break;
		case KC_RCONTROL: kc = "Right control"; break;
		case KC_MUTE: kc = "Mute"; break;
		case KC_CALCULATOR: kc = "Calculator"; break;
		case KC_PLAYPAUSE: kc = "Play/Pause"; break;
		case KC_MEDIASTOP: kc = "Stop"; break;
		case KC_VOLUMEDOWN: kc = "Volume down"; break;
		case KC_VOLUMEUP: kc = "Volume up"; break;
		case KC_WEBHOME: kc = "Web home"; break;
		case KC_NUMPADCOMMA: kc = "Numpad ,"; break;
		case KC_DIVIDE: kc = "Divide"; break;
		case KC_SYSRQ: kc = "Sys Rq"; break;
		case KC_RMENU: kc = "Right menu"; break;
		case KC_PAUSE: kc = "Pause"; break;
		case KC_HOME: kc = "Home"; break;
		case KC_UP: kc = "Up"; break;
		case KC_PGUP: kc = "Page Up"; break;
		case KC_LEFT: kc = "Left"; break;
		case KC_RIGHT: kc = "Right"; break;
		case KC_END: kc = "End"; break;
		case KC_DOWN: kc = "Down"; break;
		case KC_PGDOWN: kc = "Page Down"; break;
		case KC_INSERT: kc = "Insert"; break;
		case KC_DELETE: kc = "Delete"; break;
		case KC_LWIN: kc = "Left Super"; break;
		case KC_RWIN: kc = "Right Super"; break;
		case KC_APPS: kc = "Apps"; break;
		case KC_POWER: kc = "Power"; break;
		case KC_SLEEP: kc = "Sleep"; break;
		case KC_WAKE: kc = "Wake"; break;
		case KC_WEBSEARCH: kc = "Web search"; break;
		case KC_WEBFAVORITES: kc = "Web favorites"; break;
		case KC_WEBREFRESH: kc = "Web refresh"; break;
		case KC_WEBSTOP: kc = "Web stop"; break;
		case KC_WEBFORWARD: kc = "Web forward"; break;
		case KC_WEBBACK: kc = "Web back"; break;
		case KC_MYCOMPUTER: kc = "My computer"; break;
		case KC_MAIL: kc = "Mail"; break;
		case KC_MEDIASELECT: kc = "Media select"; break;
                default: kc = "UNKNOWN"; break;
        }

        return kc;
}

/**
 * converts a keybinding-name into a string that can be used to describe the key to end-users
 * @param keyBindings keybinding to retrieve the name for
 * @return the descriptive name of the keybinding
 */
std::string KeyBindingsManager::getKeyCodeName(ml278::KeyBindings kb)
{
	return keyBindingNames[kb];
}
